Introducción a Jupyter

Expresiones aritmeticas y algebraicas

Empezaremos esta práctica con algo de conocimientos previos de programación. Se que muchos de ustedes no han tenido la oportunidad de utilizar Python como lenguaje de programación y mucho menos Jupyter como ambiente de desarrollo para computo cientifico, asi que el primer objetivo de esta práctica será acostumbrarnos a la sintaxis del lenguaje y a las funciones que hacen especial a Jupyter.

Primero tratemos de evaluar una expresión aritmetica. Para correr el código en la siguiente celda, tan solo tienes que hacer clic en cualquier punto de ella y presionar las teclas Shift + Return.


In [ ]:
2 + 3

In [ ]:
2*3

In [ ]:
2**3

In [ ]:
sin(pi)

Sin embargo no existen funciones trigonométricas cargadas por default. Para esto tenemos que importarlas de la libreria math:


In [ ]:
from math import sin, pi
sin(pi)

Variables

Las variables pueden ser utilizadas en cualquier momento, sin necesidad de declararlas, tan solo usalas!


In [ ]:
a = 10

In [ ]:
a

Ejercicio

Ejecuta el siguiente calculo y guardalo en una variable:

$$ c = \pi *10^2 $$

Nota: Una vez que hayas concluido el calculo y guardado el valor en una variable, puedes desplegar el valor de cualquier variable al ejecutar en una celda el nombre de la variable


In [ ]:
c =

In [ ]:

Ejecuta la prueba de abajo para saber si has creado el codigo correcto


In [ ]:
from pruebas_1 import prueba_1_1
prueba_1_1(_, c)

Listas

Las listas son una manera de guardar varios datos en un mismo arreglo. Podemos tener por ejemplo:


In [ ]:
A = [2, 4, 8, 10]
A

Pero si intentamos multiplicar estos datos por un numero, no tendrá el comportamiento esperado.


In [ ]:
A*2

Funciones

Podemos definir funciones propias de la siguiente manera:


In [ ]:
f = lambda x: x**2 + 1

Esta linea de codigo es equivalente a definir una función matemática de la siguiente manera:

$$ f(x) = x^2 + 1 $$

Por lo que si la evaluamos con $x = 2$, obviamente obtendremos como resultado $5$.


In [ ]:
f(2)

Esta notación que introducimos es muy util para funciones matemáticas, pero esto nos obliga a pensar en las definiciones de una manera funcional, lo cual no siempre es la solución (sobre todo en un lenguaje con un paradigma de programación orientado a objetos).

Esta función tambien puede ser escrita de la siguiente manera:


In [ ]:
def g(x):
    y = x**2 + 1
    return y

Con los mismos resultados:


In [ ]:
g(2)

Ejercicio

Define una función que convierta grados Celsius a grados Farenheit, de acuerdo a la siguiente formula:

$$ F = \frac{9}{5} C + 32 $$

In [ ]:
def cel_a_faren(grados_cel):
    
    grados_faren = # Escribe el codigo para hacer el calculo aqui
    
    return grados_faren

Y para probar trata de convertir algunos datos:


In [ ]:
cel_a_faren(10)

In [ ]:
cel_a_faren(50)

In [ ]:
from pruebas_1 import prueba_1_2
prueba_1_2(cel_a_faren)

Ciclos de control

Cuando queremos ejecutar código varias veces tenemos varias opciones, vamos a explorar rapidamente el ciclo for.

for paso in pasos:
        ...
        codigo_a_ejecutar(paso)
        ...

En este caso el codigo se ejecutará tantas veces sean necesarias para usar todos los elementos que hay en pasos.

Por ejemplo, pordemos ejecutar la multiplicacion por 2 en cada uno de los datos:


In [ ]:
for dato in A:
    print dato*2

ó agregarlo en una lista nueva:


In [ ]:
B = []
for dato in A:
    B.append(dato*2)
    
B

y aun muchas cosas mas, pero por ahora es momento de empezar con la práctica.

Ejercicio

  • Crea una lista C con los enteros positivos de un solo digito, es decir: $\left\{ x \in \mathbb{Z} \mid 0 \leq x < 10\right\}$
  • Crea una segunda lista D con los cuadrados de cada elemento de C

In [ ]:
C = [] # Escribe el codigo para declarar el primer arreglo adentro de los corchetes

C

In [ ]:
D = []

# Escribe el codigo de tu ciclo for aqui

D

Ejecuta las pruebas de abajo


In [ ]:
from pruebas_1 import prueba_1_3
prueba_1_3(C, D)

Método de bisección

Para obtener una raiz real de un polinomio $f(x) = x^3 + 2 x^2 + 10 x - 20$ por el metodo de bisección, tenemos que primero definir dos puntos, uno que evaluado en el polinomio nos de positivo, y otro que nos de negativo. Propondremos $x_1 = 1$ y $x_2 = 2$, y los evaluaremos para asegurarnos de que cumplan lo que acabamos de pedir.


In [ ]:
f = lambda x: x**3 + 2*x**2 + 10*x - 20

In [ ]:
f(1.0)

In [ ]:
f(2.0)

Una vez que tenemos dos puntos de los que sabemos que definen el intervalo donde se encuetra una raiz, podemos empezar a iterar para descubrir el punto medio.

$$x_M = \frac{x_1 + x_2}{2}$$

Si hacemos esto ingenuamente y lo evaluamos en la función, podremos iterar manualmente:


In [ ]:
x_1, x_2 = 1.0, 2.0
xm1 = (x_1 + x_2)/2.0
f(xm1)

Y de aqui podemos notar que el resultado que nos dio esto es positivo, es decir que la raiz tiene que estar entre $x_1$ y $x_M$. Por lo que para nuestra siguiente iteración usaremos el nuevo intervalo $x_1 = 1$ y $x_2 = 2.875$, es decir que ahora asignaremos el valor de $x_M$ a $x_2$.


In [ ]:
x_1, x_2 = x_1, xm1
xm2 = (x_1 + x_2)/2.0
f(xm2)

Y podriamos seguir haciendo esto hasta que tengamos la exactitud que queremos, pero esa no seria una manera muy inteligente de hacerlo (tenemos una maquina a la que le gusta hacer tareas repetitivas y no la aprovechamos?).

En vez de eso, notemos que la formula no cambia absolutamente en nada, por lo que la podemos hacer una funcion y olvidarnos de ella.


In [ ]:
def biseccion(x1, x2):
    return (x1 + x2)/2.0

Si volvemos a ejecutar el codigo que teniamos, sustituyendo esta función, obtendremos exactamente el mismo resultado:


In [ ]:
x_1, x_2 = x_1, xm1
xm2 = biseccion(x_1, x_2)
f(xm2)

Y ahora lo que tenemos que hacer es poner una condicion para que $x_M$ se intercambie con $x_1$ ó $x_2$ dependiendo del signo.


In [ ]:
x_1, x_2 = 1.0, 2.0
xm1 = biseccion(x_1, x_2)
f(xm1)

In [ ]:
if x_2*xm1 > 0:
    x_2 = xm1
else:
    x_1 = xm1
    
xm2 = biseccion(x_1, x_2)
f(xm2)

In [ ]:
if x_2*xm2 > 0:
    x_2 = xm2
else:
    x_1 = xm2
    
xm3 = biseccion(x_1, x_2)
f(xm3)

Si, yo se que parece raro, pero si lo revisas con calma te daras cuenta que funciona.

Ya casi llegamos, tan solo tenemos que ir guardando cada una de las aproximaciones en un arreglo, y calcularemos el numero de aproximaciones necesarias para llegar a la precisión requerida. Tomemos en cuenta $\varepsilon = 0.001$. La formula para el numero de aproximaciones necesarias es:

$$n = \frac{\ln{a} - \ln{\varepsilon}}{\ln{2}}$$

donde $a$ es el tamaño del intervalo original.


In [ ]:
n = (log(1) - log(0.001))/(log(2))
n

Es decir, $n = 10$.


In [ ]:
def metodo_biseccion(funcion, x1, x2, n):
    xs = []
    for i in range(n):
        xs.append(biseccion(x1, x2))
        if funcion(x2)*funcion(xs[-1]) > 0:
            x2 = xs[-1]
        else:
            x1 = xs[-1]
    return xs[-1]

In [ ]:
metodo_biseccion(f, 1.0, 2.0, 10)

Y asi obtenemos la aproximación de nuestro ejemplo.

Problemas

  1. Copia y pega el codigo necesario (no mas, no menos) para calcular una raiz real de un polinomio en el reporte de practica.
  2. Calcule por el metodo de bisección la raiz real del siguiente polinomio (se espera un error no mayor a $0.001$) $f(x) = x^5 + 4 x^4 + 10 x^3 + x^2 + 20 x - 10$.
  3. Modifique el codigo para que en lugar de aceptar el numero de iteraciones ($n$), acepte el error maximo ($\varepsilon$).